0%

MIPS 相关理解

协处理器

在MIPS体系结构中,最多支持4个协处理器(Co-Processor)。其中,协处理器CP0是体系结构中必须实现的。它起到控制CPU的作用。MMU、异常处理、乘除法等功能,都依赖于协处理器CP0来实现。它是MIPS的精髓之一,也是打开MIPS特权级模式的大门。

   - Register 0: Index,作为MMU的索引用。将来讨论MMU和TLB时会详解之。
   - Register 2, EntryLo0,访问TLB Entry偶数页中的地址低32Bit用。同上,在MMU和TLB的相关章节中详解。
   - Register 3, EntryLo1,访问TLB Entry奇数页中的地址低32Bit用。
   - Register 4, Context,用以加速TLB Miss异常的处理。
   - Register 5, PageMask,用以在MMU中分配可变大小的内存页。
   - Register 8, BadVAddr,在系统捕获到TLB Miss或Address Error这两种Exception时,发生错误的虚拟地址会储存在该寄存器中。对于引发Exception的Bug的定位来说,这个寄存器非常重要。
   - `Register 9`, Count,这个寄存器是R4000以后的MIPS系统引入的。它是一个计数器,`计数频率是系统主频的1/2`。BCM1125/1250,RMI XLR系列以及Octeon的Cavium处理器均支持该寄存器。对于操作系统来说,`可以通过读取该寄存器的值来获取tick的时基`。`在系统性能测试中,利用该寄存器也可以实现打点计数`。
   - Register 10,EntryHi,这个寄存器同EntryLo0/1一样,用于MMU中。以后会详述。
   - Register 11,Compare,配合Count使用。当Compare和Count的值相等的时候,会触发一个硬件中断(Hardware Interrupt),并且总是使用Cause寄存器的IP7位。  
   - Register 12,Status,用于处理器状态的控制。
   - Register 13,Cause,这个寄存器体现了处理器异常发生的原因。
   -   Register 14,EPC,这个寄存器存放异常发生时,系统正在执行的指令的地址。
   -   Register 15,PRID,这个寄存器是只读的,标识处理器的版本信息。向其中写入无意义。
  •   Register 18/19,WatchLo/WatchHi,这对寄存器用于设置硬件数据断点(Hardware Data Breakpoint)。该断点一旦设定,当CPU存取这个地址时,系统就会发生一个异常。这个功能广泛应用于调试定位内存写坏的错误。
      Register 28/29,TagLo和TagHi,用于高速缓存(Cache)管理。

相关链接 协处理器CP0

常用汇编指令

MFC0、MTC0、DMFC0、DMTC0可以完成通用寄存器和CP0寄存器之间的数据传送。比如我们可以通过下面指令来获取epc寄存器的值。

             mfc0 t1,$14           # 从CP0 取epc寄存器($14)数据,存到通用寄存器t0

mfc0完成从cp0寄存器获取一个32位数据(如果cp0寄存器是64位数据则只取其低32位。)到通用寄存器,如果要获取的是64位数据则需要使用dmfc0指令。

注意:和CP0相关的指令执行都需要特权模式,比如上面的mfc0指令的运行需要root权限或者sudo才可以运行。所以在一般用户程序中很少见到这些CP0相关的控制指令。

相关理解总结在 linux 中链接脚本 ld 文件详解

这里总结下用到的大概率用到的知识点:

LMA VMA

目标文件的每个 section 至少包含两个信息: 名字和大小. 大部分 section 还包含与它相关联的一块数据, 称为 section contents(section 内容). 一个 section 可被标记为 “loadable(可加载的)” 或“allocatable(可分配的)”.

  • loadable section: 在输出文件运行时, 相应的 section 内容将被载入进程地址空间中.

  • allocatable section: 内容为空的 section 可被标记为 “可分配的”. 在输出文件运行时, 在进程地址空间中空出大小同 section 指定大小的部分. 某些情况下, 这块内存必须被置零.

如果一个 section 不是 “可加载的” 或“可分配的”, 那么该 section 通常包含了调试信息. 可用 objdump -h 命令查看相关信息.

每个 “可加载的” 或“可分配的”输出 section 通常包含两个地址: VMA(virtual memory address 虚拟内存地址或程序地址空间地址)和 LMA(load memory address 加载内存地址或进程地址空间地址). 通常 VMA 和 LMA 是相同的.

在嵌入式系统中, 经常存在加载地址和执行地址不同的情况: 比如将输出文件加载到开发板的 flash 中 (由 LMA 指定), 而在运行时将位于 flash 中的输出文件复制到 SDRAM 中 (由 VMA 指定).

LMA是加载地址(镜像执行之前位于存储器中的地址, 称为加载时地址), 需要将该地址的数据拷贝到对应的VMA段下. VMA是实际映射到内存中的地址, 可以通过map文件查看

-M 或者 -Map 指令指定输出到map文件, map 应该类似于进程执行时 map 内存映射 (刨除基地址)

定位符号 .

. 是一个特殊的符号,它是定位器,一个位置指针, 指向程序地址空间内的某位置 (如果它在 SECTIONS 命令内的某 section 描述内, 指的是在该section 内的偏移)

该符号只能在 SECTIONS 命令内使用 .始终指的是运行时的地址 VMA, 与LMA无关

SECTIONS 命令

SECTIONS 命令告诉 ld 如何把输入文件的 sections 映射到输出文件的各个 section: 如何将输入 section 合为输出 section; 如何把输出 section 放入程序地址空间 (VMA) 和进程地址空间(LMA). 该命令格式如下:

1
2
3
4
5
6
SECTIONS
{
SECTIONS-COMMAND
SECTIONS-COMMAND

}

SECTION-COMMAND 有四种:

  • (1) ENTRY 命令

  • (2) 符号赋值语句

  • (3) 一个输出 section 的描述 (output section description)

  • (4) 一个 section 叠加描述 (overlay description)

如果整个连接脚本内没有 SECTIONS 命令, 那么 ld 将所有同名输入 section 合成为一个输出 section 内, 各输入 section 的顺序为它们被连接器发现的顺序.

如果某输入 section 没有在 SECTIONS 命令中提到, 那么该 section 将被直接拷贝成输出 section。

输出 section 描述

输出 section 描述具有如下格式:

1
2
3
4
5
6
SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND

} [>REGION] [AT>LMA_REGION] [:PHDR HDR ...] [=FILLEXP]

[ ] 内的内容为可选选项, 一般不需要.

每个OUTPUT-SECTION-COMMAND为以下四种之一,

  • 符号赋值语句
  • 一个输入 section 描述
  • 直接包含的数据值
  • 一个特殊的输出 section 关键字
前面部分 SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
输出 section 名字 (SECTION)

输出 section 名字必须符合输出文件格式要求,比如:a.out 格式的文件只允许存在. text、.data 和. bss section 名。而有的格式只允许存在数字名字,那么此时应该用引号将所有名字内的数字组合在一起;另外,还有一些格式允许任何序列的字符存在于 section 名字内,此时如果名字内包含特殊字符 (比如空格、逗号等),那么需要用引号将其组合在一起。

输出 section 地址 (ADDRESS)

ADDRESS 是一个表达式,它的值用于设置 VMA

  • 如果没有该选项且有 REGION 选项,那么连接器将根据 REGION 设置 VMA;

  • 如果也没有 REGION 选项,那么连接器将根据定位符号 . 的值设置该 section 的 VMA,将定位符号的值调整到满足输出 section 对齐要求后的值,

输出 section 的对齐要求为:该输出 section 描述内用到的所有输入 section 的对齐要求中最严格的。

例子:

1
2
1   .text . : {*(.text) }
2 .text : {*(.text) }

第一个 . 定义了输出VMA的地址,说明是根据当前的定位指针的偏移设置 VMA的

第二个 没有定义 输出DMA,也没有REGION, 则根据定位符号 . 对齐后的值 输出 VMA

ADDRESS 可以是一个任意表达式,比如 ALIGN(0×10) 这将把该 section 的 VMA 设置成定位符号的修调值,满足 16 字节对齐后的。

注意:设置 ADDRESS 值,将更改定位符号的值。

TYPE

每个输出 section 都有一个类型,如果没有指定 TYPE 类型,那么连接器根据输出 section 引用的输入 section 的类型设置该输出 section 的类型。

它可以为以下五种值,

  1. NOLOAD :该 section 在程序运行时,不被载入内存。
  2. DSECT
  3. COPY
  4. INFO
  5. OVERLAY 支持这些类型名称以实现向后兼容,并且很少使用。 它们都具有相同的效果:该段应标记为不可分配,以便在程序运行时不为该段分配内存
[AT(LMA)]

默认情况下,LMA 等于 VMA,但可以通过关键字 AT() 指定 LMA。

用关键字 AT() 指定,括号内包含表达式,表达式的值用于设置 LMA。如果不用 AT() 关键字,那么可用 AT>LMA_REGION 表达式设置指定该 section 加载地址的范围。 这个属性主要用于构件 ROM 境象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SECTIONS
{
.text 0×1000 :
{
*(.text);
_etext = . ;
}
.mdata 0×2000 : AT (ADDR (.text) + SIZEOF (.text) )
{
_data = . ;
*(.data);
_edata = . ;
}
.bss 0×3000 :
{
bstart = . ;
*(.bss) *(COMMON) ;
_bend = . ;
}
}
1
2
3
4
5
6
7
8
extern char etext, data, edata, bstart, _bend;
char *src = &_etext;
char *dst = &_data;
while (dst < &_edata) {
dst++ = src++;
}
for (dst = &bstart; dst< &_bend; dst++)
*dst = 0;

此程序将处于 ROM内的已初始化数据拷贝到 mdata段的相应区域,并将未初始化数据置零

中间部分 OUTPUT-SECTION-COMMAND

每个OUTPUT-SECTION-COMMAND为以下四种之一,

  • 符号赋值语句

  • 一个输入 section 描述

  • 直接包含的数据值

  • 一个特殊的输出 section 关键字
输入 section 描述

最常见的输出 section 描述命令是输入 section 描述。

输入 section 描述是最基本的连接脚本描述。

语法 FILENAME([EXCLUDE_FILE (FILENAME1 FILENAME2 ...) SECTION1 SECTION2 ...)

  • FILENAME 文件名,可以是一个特定的文件的名字,也可以是一个字符串模式。

  • SECTION 名字,可以是一个特定的 section 名字,也可以是一个字符串模式

    例子:

    • *(.text) :表示所有输入文件的. text section

    • (*(EXCLUDE_FILE ( *crtend.o *otherfile.o) .ctors)) :表示除 crtend.o、otherfile.o 文件外的所有输入文件的. ctors section。

    • data.o(.data) :表示 data.o 文件的. data section

    • data.o :表示 data.o 文件的所有 section

    • *(.text .data) :表示所有文件的. text section 和. data section,顺序是:第一个文件的. text section,第一个文件的. data section, 第二个文件的. text section,第二个文件的. data section,…

    • *(.text) * (.data) :表示所有文件的. text section 和. data section,顺序是:第一个文件的. text section,第二个文件的. text section,…,最后一个文件的. text section,第一个文件的. data section,第二个文件的. data section,…,最后一个文件的. data section

前面部分 SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
后半部分 [>REGION] [AT>LMA_REGION] [:PHDR :FILEHDR …] [=FILLEXP]
[>REGION]

可以将输出 section 放入预先定义的内存区域内

1
2
MEMORY { rom : ORIGIN = 0×1000, LENGTH = 0×1000 }
SECTIONS {ROM : { *(.text) } >rom }

这个地方应该是放在VMA中, 注意这里输出到 预定的内存区域后, 连带会把 . 改为 预定内存区域的地址(第一次出现), 然后执行section 内的语句

ld脚本section 是按顺序解析的, 第一次出现时 会将 .定界, 后面的section 如果都放在和该section相同的内存区域时, . 会累加 section的size.

[:PHDR :FILEHDR … ] 这里显示的是段名

不需要深究, 详细见https://sourceware.org/binutils/docs/ld/PHDRS.html

linker脚本中的PHDRS命令需要与SECTIONS命令结合起来看,否则不容易理解.

1
2
3
4
5
6
7
8
9
10
PHDRS
{
text PT_LOAD FILEHDR PHDRS ;
data PT_LOAD ;
}
SECTIONS
{
.text : { *(.text) } :text // :text 是 PHDRS 里定义的段名
.data : { *(.data) } :data // :data 是 PHDRS 里定义的段名
}

ld以该脚本对输入文件进行链接,输出文件中将会包含两个section:.text和.data
以SECTIONS命令中的第一行为例: .text : { *(.text) } :text
这句话是说将输入文件中所有的.text section (中间的 *(.text)) 都链接到输出文件的.text section中.
并将输出文件中的.text section分配到PHDRS命令中定义的名为text的segment中.

PHDRS命令中,
text PT_LOAD FILEHDR PHDRS ;
该语句定义了名为text的一个segment
它的属性为PT_LOAD, 表示它是一个可以被加载的segment
FILEHDRPHDRS是它的追加属性,分别表示在这个segment中必须包含ELF文件头必须包含ELF程序文件头

You may use the FILEHDR and PHDRS keywords after the program header type to further describe the contents of the segment. The FILEHDR keyword means that the segment should include the ELF file header. The PHDRS keyword means that the segment should include the ELF program headers themselves. If applied to a loadable segment (PT_LOAD), all prior loadable segments must have one of these keywords.

The ELF object file format uses program headers, also knows as segments. The program headers describe how the program should be loaded into memory. You can print them out by using the objdump -p

The linker will create reasonable program headers by default. However, in some cases, you may need to specify the program headers more precisely. You may use the PHDRS command for this purpose. When the linker sees the PHDRS command in the linker script, it will not create any program headers other than the ones specified.

[=FILLEXP] 这里显示的是段名

= FILLEXP 属性作用于整个输出 section 区域

在当前输出 section 内可能存在未描述的存储区域 (比如由于对齐造成的空隙),可以用 FILL(EXPRESSION) 命令决定这些存储区域的内容

它的作用如同 FILL()命令,但是 FILL 命令只作用于该 FILL 指令之后的 section 区域,而 = FILLEXP 属性作用于整个输出 section 区域,且 FILL 命令的优先级更高!!!

1
SECTIONS { .text : { *(.text) } =0x90909090 } // 此处=0x90909090 为 [=FILLEXP]

(for example, gaps left due to the required alignment of input sections) will be filled with the value, repeated as necessary. If the fill expression is a simple hex number, ie. a string of hex digit starting with ‘0x’ and without a trailing ‘k’ or ‘M’, then an arbitrarily long sequence of hex digits can be used to specify the fill pattern; Leading zeros become part of the pattern too. For all other cases, including extra parentheses or a unary +, the fill pattern is the four least significant bytes of the value of the expression.

In all cases, the number is big-endian. 见 https://sourceware.org/binutils/docs/ld/Output-Section-Fill.html

输入 section 和垃圾回收:

在连接命令行内使用了选项–gc-sections 后,连接器可能将某些它认为没用的 section 过滤掉,此时就有必要强制连接器保留一些特定的 section,可用 KEEP() 关键字达此目的。如 KEEP((.text)) 或 KEEP(SORT()(.text))

硬件寄存器

即使有tlb mmu的情况下, 硬件寄存器地址所在的地址段 就是真实的物理地址, 是一一映射的

这里注意的一点是 地址的快速运算, 看代码时, 利用gdb 将地址寄存器的内容快速打出, 不需要频繁加log.

DMA

流控制器

决定DMA块传输长度并终止它的设备被称为流控制器(DW_ahb_dmac或源/目标外围设备)

  • 如果在发起传输前就知道传输长度,则dma自己是作为流控制器
  • 传送长度未知时,源外设或目标外设是流控制器模式。外设需要在流程中主动发起结束请求, 这种情况下只能选择软件握手。
  • 内存不可能是流控制器, 仅限能够发出传输结束信号的外设支持此功能

源、目标和传输模式

存储器到外设模式

FIFO 模式

这种模式,只要使能数据流(DMA_SxCR 寄存器中的位 EN 置 1),存储器数据就会传输到 FIFO 中,发生外设请求,FIFO 数据会移出并存储到目标地址。

当 FIFO 小于阈值,存储器的数据会重新装载 FIFO

提高带宽的特殊模式。当启用时,通道会等到目标设备的FIFO小于半满时才从源外设获取数据,等到源设备的FIFO大于或等于半满时才会向目的外设发送数据。正因为如此,通道可以使用突发方式传输数据,这样就不需要在每一次AHB传输中对AHB主接口进行仲裁。当不启用该模式时,通道只等待FIFO可以传输或接受单次AHB传输后才会请求主总线接口。

直连模式

使能数据流时,DMA 传输存储器的第一个数据到内部 FIFO,发生外设请求时,DMA 把预装在值发送的目标地址,然后进行下一个数据的传输。预装载的数据大小为 DMA_SxCR 寄存器中 PSIZE 位字段的值

传输停止条件,下列满足一条即可:

  • DMA_SxNDTR block_ts 寄存器达到零
  • 外设请求传输终止
  • DMA_SxCR 寄存器中的 EN chen 位由软件清零

存储器到外设模式和外设到存储器模式一样,同样需要对应数据流赢得仲裁,才会启动传输

握手接口

DEST_PER

HS_SEL_DST字段为0时(即配置的硬件仲裁), 分配一个硬件握手接口(0 - 15)到通道n的目的地。如配置为1(软件仲裁), 该字段将被忽略。

然后,通道可以通过指定的硬件握手接口与连接到该接口的目标外围设备通信。

为了正确的DMA操作,只有一个外围设备(源或目标)应该被分配到同一个握手接口。

SRC_PER

同上, 与HS_SEL_SRC字段配合

? 对于外设来说, 握手接口的类型取决与其是否是流控制器, 如果是流控制器, 是软件握手接口; 不是流控制器, 即在发起dma的传输之前, 已经知道了传输大小, 需要将大小配置给dma 控制器, 此时外设只能选择硬件握手接口. ? XXXXXX 理解错误

参考dma 设计pdf 3.8 章节 3.8.1 3.8.2

这个地方有疑问, 如果外设不是流控制器,又配置的软件握手, 需要另外的软件逻辑同步外设的状态与dma的传输状态是一致的, 即额外的软件握手通信的流程。否则只配置软件握手, 是无法正常work的。

方案

代码混淆

llvm 平台混淆器

代理脱壳的主体代码经过了代码混淆

字符串加密

1
-mllvm -sobf

对字符串常量进行加密, 用以屏蔽通过打印的相关log 反追踪到代码的位置

控制流扁平化

这个模式主要是把一些if-else语句,嵌套成do-while语句

1
-mllvm -fla

指令替换

这个模式主要用功能上等效但更复杂的指令序列替换标准二元运算符(+ , – , & , | 和 ^)

1
-mllvm -sub

虚假控制流程

这个模式主要嵌套几层判断逻辑,一个简单的运算都会在外面包几层if-else,所以这个模式加上编译速度会慢很多因为要做几层假的逻辑包裹真正有用的代码。

1
-mllvm -bcf

加壳保护

算法方提供静态库, 静态库是未加密未加混淆带完整符号信息

加密侧需要将算法的关键函数进行加壳保护, 该加壳层可以称为代理
既需要对算法的关键函数进行加壳脱壳, 也需要将算法的对外api 进行重封装.
外部调用的是代理后的api.

lib 库脱壳

maps 信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
1|grus:/ $ cat /proc/self/maps
61f3191000-61f31bb000 r--p 00000000 fd:00 1379 /system/bin/toybox
61f31bb000-61f31fe000 --xp 0002a000 fd:00 1379 /system/bin/toybox
61f31fe000-61f3202000 rw-p 0006d000 fd:00 1379 /system/bin/toybox
61f3202000-61f3206000 r--p 00071000 fd:00 1379 /system/bin/toybox
61f3206000-61f320b000 rw-p 00000000 00:00 0
7b1e7d7000-7b1e7d9000 r--p 00000000 fd:00 4159 /system/lib64/libnetd_client.so
7b1e7d9000-7b1e7db000 --xp 00002000 fd:00 4159 /system/lib64/libnetd_client.so
7b1e7db000-7b1e7dc000 rw-p 00004000 fd:00 4159 /system/lib64/libnetd_client.so
7b1e7dc000-7b1e7dd000 r--p 00005000 fd:00 4159 /system/lib64/libnetd_client.so
7b1e7dd000-7b1e7de000 rw-p 00000000 00:00 0 [anon:.bss]
7b1e800000-7b1f000000 rw-p 00000000 00:00 0 [anon:libc_malloc]
7b1f02a000-7b1f08e000 rw-p 00000000 00:00 0 [anon:linker_alloc]
7b1f08e000-7b1f096000 r--p 00000000 fd:00 4353 /system/lib64/libz.so
7b1f096000-7b1f0a6000 --xp 00008000 fd:00 4353 /system/lib64/libz.so
7b1f0a6000-7b1f0a7000 rw-p 00018000 fd:00 4353 /system/lib64/libz.so
7b1f0a7000-7b1f0a8000 r--p 00019000 fd:00 4353 /system/lib64/libz.so
7b1f0e1000-7b1f0e2000 r--p 00000000 fd:00 4180 /system/lib64/libpackagelistparser.so
7b1f0e2000-7b1f0e3000 --xp 00001000 fd:00 4180 /system/lib64/libpackagelistparser.so
7b1f0e3000-7b1f0e4000 rw-p 00002000 fd:00 4180 /system/lib64/libpackagelistparser.so
7b1f0e4000-7b1f0e5000 r--p 00003000 fd:00 4180 /system/lib64/libpackagelistparser.so
7b1f131000-7b1f172000 r--p 00000000 fd:00 308 /apex/com.android.runtime/lib64/bionic/libc.so
7b1f172000-7b1f21e000 --xp 00041000 fd:00 308 /apex/com.android.runtime/lib64/bionic/libc.so
7b1f21e000-7b1f221000 rw-p 000ed000 fd:00 308 /apex/com.android.runtime/lib64/bionic/libc.so
7b1f221000-7b1f228000 r--p 000f0000 fd:00 308 /apex/com.android.runtime/lib64/bionic/libc.so
7b1f228000-7b1f22c000 rw-p 00000000 00:00 0 [anon:.bss]
7b1f22c000-7b1f22d000 r--p 00000000 00:00 0 [anon:.bss]
7b1f22d000-7b1f43f000 rw-p 00000000 00:00 0 [anon:.bss]
7b1f451000-7b1f459000 r--p 00000000 fd:00 3872 /system/lib64/libbase.so
7b1f459000-7b1f462000 --xp 00008000 fd:00 3872 /system/lib64/libbase.so
7b1f462000-7b1f463000 rw-p 00011000 fd:00 3872 /system/lib64/libbase.so
7b1f463000-7b1f464000 r--p 00012000 fd:00 3872 /system/lib64/libbase.so
7b1f464000-7b1f465000 rw-p 00000000 00:00 0 [anon:.bss]
7b1f4aa000-7b1f4fa000 r--p 00000000 fd:00 3894 /system/lib64/libc++.so
7b1f4fa000-7b1f56f000 --xp 00050000 fd:00 3894 /system/lib64/libc++.so
7b1f56f000-7b1f570000 rw-p 000c5000 fd:00 3894 /system/lib64/libc++.so
7b1f570000-7b1f578000 r--p 000c6000 fd:00 3894 /system/lib64/libc++.so
7b1f578000-7b1f57c000 rw-p 00000000 00:00 0 [anon:.bss]
7b1f5a4000-7b1f5a6000 r--p 00000000 fd:00 3914 /system/lib64/libcgrouprc.so
7b1f5a6000-7b1f5a7000 --xp 00002000 fd:00 3914 /system/lib64/libcgrouprc.so
7b1f5a7000-7b1f5a8000 rw-p 00003000 fd:00 3914 /system/lib64/libcgrouprc.so
7b1f5a8000-7b1f5a9000 r--p 00004000 fd:00 3914 /system/lib64/libcgrouprc.so
7b1f5a9000-7b1f5aa000 rw-p 00000000 00:00 0 [anon:.bss]
7b1f5fd000-7b1f613000 r--p 00000000 fd:00 4197 /system/lib64/libprocessgroup.so
7b1f613000-7b1f63a000 --xp 00016000 fd:00 4197 /system/lib64/libprocessgroup.so
7b1f63a000-7b1f63b000 rw-p 0003d000 fd:00 4197 /system/lib64/libprocessgroup.so
7b1f63b000-7b1f63d000 r--p 0003e000 fd:00 4197 /system/lib64/libprocessgroup.so
7b1f63d000-7b1f63e000 rw-p 00000000 00:00 0 [anon:.bss]
7b1f647000-7b1f64e000 r--p 00000000 fd:00 3969 /system/lib64/libcutils.so
7b1f64e000-7b1f656000 --xp 00007000 fd:00 3969 /system/lib64/libcutils.so
7b1f656000-7b1f657000 rw-p 0000f000 fd:00 3969 /system/lib64/libcutils.so
7b1f657000-7b1f659000 r--p 00010000 fd:00 3969 /system/lib64/libcutils.so
7b1f659000-7b1f65a000 rw-p 00000000 00:00 0 [anon:.bss]
7b1f6a0000-7b1f716000 r--p 00000000 fd:00 3965 /system/lib64/libcrypto.so
7b1f716000-7b1f7cd000 --xp 00076000 fd:00 3965 /system/lib64/libcrypto.so
7b1f7cd000-7b1f7ce000 rw-p 0012d000 fd:00 3965 /system/lib64/libcrypto.so
7b1f7ce000-7b1f7df000 r--p 0012e000 fd:00 3965 /system/lib64/libcrypto.so
7b1f7df000-7b1f7e1000 rw-p 00000000 00:00 0 [anon:.bss]
7b1f800000-7b1f806000 r--p 00000000 fd:00 4091 /system/lib64/liblog.so
7b1f806000-7b1f815000 --xp 00006000 fd:00 4091 /system/lib64/liblog.so
7b1f815000-7b1f816000 rw-p 00015000 fd:00 4091 /system/lib64/liblog.so
7b1f816000-7b1f817000 r--p 00016000 fd:00 4091 /system/lib64/liblog.so
7b1f817000-7b1f818000 rw-p 00000000 00:00 0 [anon:.bss]
7b1f848000-7b1f84f000 r--p 00000000 fd:00 4230 /system/lib64/libselinux.so
7b1f84f000-7b1f85f000 --xp 00007000 fd:00 4230 /system/lib64/libselinux.so
7b1f85f000-7b1f860000 rw-p 00017000 fd:00 4230 /system/lib64/libselinux.so
7b1f860000-7b1f861000 r--p 00018000 fd:00 4230 /system/lib64/libselinux.so
7b1f861000-7b1f863000 rw-p 00000000 00:00 0 [anon:.bss]
7b1f8a3000-7b1f8a4000 r--p 00000000 fd:00 309 /apex/com.android.runtime/lib64/bionic/libdl.so
7b1f8a4000-7b1f8a5000 --xp 00001000 fd:00 309 /apex/com.android.runtime/lib64/bionic/libdl.so
7b1f8a5000-7b1f8a6000 r--p 00002000 fd:00 309 /apex/com.android.runtime/lib64/bionic/libdl.so
7b1f8a6000-7b1f8a7000 rw-p 00000000 00:00 0 [anon:.bss]
7b1f8c4000-7b1f8d6000 r--p 00000000 fd:00 310 /apex/com.android.runtime/lib64/bionic/libm.so
7b1f8d6000-7b1f8fa000 --xp 00012000 fd:00 310 /apex/com.android.runtime/lib64/bionic/libm.so
7b1f8fa000-7b1f8fb000 rw-p 00036000 fd:00 310 /apex/com.android.runtime/lib64/bionic/libm.so
7b1f8fb000-7b1f8fc000 r--p 00037000 fd:00 310 /apex/com.android.runtime/lib64/bionic/libm.so
7b1f8fc000-7b1f8fd000 rw-p 00000000 00:00 0 [anon:.bss]
7b1f927000-7b1f947000 r--p 00000000 fd:00 4182 /system/lib64/libpcre2.so
7b1f947000-7b1f973000 --xp 00020000 fd:00 4182 /system/lib64/libpcre2.so
7b1f973000-7b1f974000 rw-p 0004c000 fd:00 4182 /system/lib64/libpcre2.so
7b1f974000-7b1f975000 r--p 0004d000 fd:00 4182 /system/lib64/libpcre2.so
7b1f98b000-7b1f9ab000 r--s 00000000 00:10 14093 /dev/__properties__/u:object_r:heapprofd_prop:s0
7b1f9ab000-7b1f9cb000 r--s 00000000 00:10 14061 /dev/__properties__/u:object_r:exported2_default_prop:s0
7b1f9cb000-7b1f9eb000 r--s 00000000 00:10 14044 /dev/__properties__/u:object_r:debug_prop:s0
7b1f9eb000-7b1f9ec000 rw-p 00000000 00:00 0 [anon:bionic_alloc_small_objects]
7b1f9f4000-7b1fa14000 r--s 00000000 00:10 14211 /dev/__properties__/properties_serial
7b1fa14000-7b1fa22000 r--s 00000000 00:10 13997 /dev/__properties__/property_info
7b1fa22000-7b1fa23000 ---p 00000000 00:00 0
7b1fa23000-7b1fa26000 rw-p 00000000 00:00 0
7b1fa26000-7b1fa27000 ---p 00000000 00:00 0
7b1fa8b000-7b1fb53000 r--p 00000000 00:00 0 [anon:linker_alloc]
7b1fb53000-7b1fb57000 rw-p 00000000 00:00 0 [anon:bionic_alloc_small_objects]
7b1fb58000-7b1fb5a000 rw-p 00000000 00:00 0 [anon:bionic_alloc_small_objects]
7b1fb5a000-7b1fb5d000 rw-p 00000000 00:00 0 [anon:System property context nodes]
7b1fb5d000-7b1fb5f000 rw-p 00000000 00:00 0 [anon:bionic_alloc_small_objects]
7b1fb60000-7b1fb66000 rw-p 00000000 00:00 0 [anon:bionic_alloc_small_objects]
7b1fb66000-7b1fb86000 r--s 00000000 00:10 14072 /dev/__properties__/u:object_r:exported_default_prop:s0
7b1fb86000-7b1fb88000 rw-p 00000000 00:00 0 [anon:bionic_alloc_small_objects]
7b1fb88000-7b1fbec000 r--p 00000000 00:00 0 [anon:linker_alloc]
7b1fbec000-7b1fc0c000 r--s 00000000 00:10 14044 /dev/__properties__/u:object_r:debug_prop:s0
...

供外部调用的算法 api 函数名 加密保护, 避免通过api 函数名找到函数入口.
不是原地解密, 而是新开辟一块匿名内存, 将原程序的文本段等拷贝到新的匿名内存上, 拷贝的起始位置并不是0, 而是从某个offset处开始拷贝, 即使反追踪到该匿名内存位置, 也不知道offset 是多少, 无法直接搬回lib库通过pc工具进行解析lib库.

其他加密杂项

  • lib 中加入ptrace 调用, 限制动态调试
  • 主代理代码经过混淆+加密调用算法api调用关系来保护算法入口, 通过加壳保护算法.
  • 脱壳算法代码内实现, 不调用其他加密库, 防止通过反追踪加密库api调用猜测脱壳算法.
  • 脱壳算法关键特征信息隐藏, 防止通过查看加密算法特征猜测脱壳算法.
  • 对语音算法中的非api 的关键函数, 主题代理代码中会加入部分类同名伪代码进行干扰.
  • 语音算法api 函数加密更名, 防止通过dump 猜测到函数主体.
  • 对语音算法关键函数进行加壳, 而不是整体加壳, 多个函数之间形成链式加密, 同时其在内存上的排布也是一个一个空洞, 而非连续的整体, 进一步加大破解难度
  • 识别手机root debuggable信息, 在debuggable手机上直接退出, 不进行脱壳.
  • 识别手机串号, 只有授权的才会脱壳运行.

ARMv8 有 el0-el3 四个异常等级, 有 NS 和 S 两个状态, ARMv8 对 TZ 技术有先天性的优势.

ARMv7 基础

在 armv7 上首次添加了 trustzone, 在 arm 原有七种运行模式的基础上扩展出了 monitor 模式
normal world 和 sec world 的切换是由 monitor 模式下的程序来完成.

arm 原有的其中运行模式

  • usr 模式(用户模式): 正常程序运行时的模式
  • fiq 模式(快速中断模式): 当配置有快速中断时, 如果产生 fiq 事件, arm 会切换到该模式
  • irq 模式(用户模式): 中断模式, 用于通用中断处理, 被 ROS 使用
  • svc 模式(管理模式): 操作系统使用的保护模式
  • abt 模式(数据访问终止模式): 当数据或者指令取值时终止则会进入该模式
  • und 模式(未定义指令模式): 当未定义指令执行会进入该模式
  • sys 模式(系统模式): 运行具有特权的操作系统任务

这些模式都是一种硬件反应态,AXI 总线强烈依赖访问时的模式。
支持 TZ 后, ARM 增加了 monitor 模式. monitor 模式是共享的, 在 monitor 模式下, 每种状态具有独立的七种模式

armv7 安全位扩展

在支持 TZ 后, ARM 在 axi 总线上增加了一个 NS 位(安全状态位), 用来标识当前的数据\指令是属于安全世界还是非安全世界, NS bit 保存在 scr 寄存器的 0 bit, NS=1, normal world, NS=0, sec world

除了对总线进行扩展之外,ARM 对 MMU 和 Cache 也同样进行了安全状态位的扩展,用于标记 MMU 中存放的物理内存映射后的地址是属于安全内存地址还是非安全地址,而对于 Cache 该位会被用来标记当前的 Cache 是属于安全态的 Cache 还是非安全态的 Cache。当 ARM 核访问物理地址时,会对该虚拟地址的安全状态位进行检查,而在访问物理内存时安全扩展组件会对地址进行权限检查,该权限检查操作属于硬件级别的检查,不受软件的控制。关于安全地址的配置则是在 IC 设计时通过配置安全组件的参数来设定的

armv7 vs armv8 特权等级(运行模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

LOCAL_FUNC sm_vect_table , :, align=32
UNWIND( .cantunwind)
b . /* Reset */
b . /* Undefined instruction */
b sm_smc_entry /* Secure monitor call */
b . /* Prefetch abort */
b . /* Data abort */
b . /* Reserved */
b . /* IRQ */
b sm_fiq_entry /* FIQ */
END_FUNC sm_vect_table

| ------------------ | -------------- | ---------------------- |
| `VBAR_ELn + 0x000` | Synchronous | Current EL with SP0 |
| `+ 0x080` | IRQ/vIRQ | Current EL with SP0 |
| `+ 0x100` | FIQ/vFIQ | Current EL with SP0 |
| `+ 0x180` | SError/vSError | Current EL with SP0 |
| ------------------ | -------------- | ---------------------- |
| `+ 0x200` | Synchronous | Current EL with SPx |
| `+ 0x280` | IRQ/vIRQ | Current EL with SPx |
| `+ 0x300` | FIQ/vFIQ | Current EL with SPx |
| `+ 0x380` | SError/vSError | Current EL with SPx |
| ------------------ | -------------- | ---------------------- |
| `+ 0x400` | Synchronous | Lower EL using AArch64 |
| `+ 0x480` | IRQ/vIRQ | Lower EL using AArch64 |
| `+ 0x500` | FIQ/vFIQ | Lower EL using AArch64 |
| `+ 0x580` | SError/vSError | Lower EL using AArch64 |
| ------------------ | -------------- | ---------------------- |
| `+ 0x600` | Synchronous | Lower EL using AArch32 |
| `+ 0x680` | IRQ/vIRQ | Lower EL using AArch32 |
| `+ 0x700` | FIQ/vFIQ | Lower EL using AArch32 |
| `+ 0x780` | SError/vSError | Lower EL using AArch32 |
| ------------------ | -------------- | ---------------------- |

armv7 进入 monitor 模式的处理流程

monitor 模式的 vec 入口 放在了 optee_os 中, armv7 没有 ATF, armv8 上才有 ATF.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
LOCAL_FUNC sm_smc_entry , :      // 进入 monitor 模式
UNWIND( .cantunwind)
srsdb sp!, #CPSR_MODE_MON //将当前模式的lr和spsr寄存器中的值分别存储在monitor模式的sp中
push {r0-r7} //将r0到r7中的值压入栈(sp)

clrex /* Clear the exclusive monitor */ //独占清除,可以将关系紧密的独占访问监控器返回为开放模式

/* Find out if we're doing an secure or non-secure entry */
read_scr r1 //获取当前scr寄存器中的值,并将值保存在r1寄存器中
tst r1, #SCR_NS //读SCR 的 NS bit
bne .smc_from_nsec //如果NS=0, 表明来自于非安全世界, NS=1, 来自于 sec world. 来自于非安全世界时, 进入 smc_from_nsec 函数处理, 就不回来了


============================== 来自于安全世界时的处理流==============================
// 当前处于安全世界中
/*
* As we're coming from secure world (NS bit cleared) the stack
* pointer points to sm_ctx.sec.r0 at this stage. After the
* instruction below the stack pointer points to sm_ctx.
*/
sub sp, sp, #(SM_CTX_SEC + SM_SEC_CTX_R0) //当前sp的值减去offset就可以得到Secure World的运行栈地址

/* Save secure context */
add r0, sp, #SM_CTX_SEC //将sp的值加上secure world context的长度保存在r0寄存器中 r0 存放sec world context
bl sm_save_unbanked_regs //从r0 寄存器的地址的位置开始 保存sec world 中八种模式的主要寄存器的值

/*
* On FIQ exit we're restoring the non-secure context unchanged, on
* all other exits we're shifting r1-r4 from secure context into
* r0-r3 in non-secure context.
*/
add r8, sp, #(SM_CTX_SEC + SM_SEC_CTX_R0) // r8 保存 secure world 运行栈
ldm r8, {r0-r4} // r0-r4 读到 r8
mov_imm r9, TEESMC_OPTEED_RETURN_FIQ_DONE //r9 赋值为 TEESMC_OPTEED_RETURN_FIQ_DONE
cmp r0, r9 // r0 是否等于 r9
addne r8, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R0) // 如果 r0 != r9, r8 = sp + non_sec context + non_sec_r0 (保存八种主要模式寄存器的地方)
stmne r8, {r1-r4} // 如果 r0 != r9, r1-r4 一次加载到 sp + non_sec context + non_sec_r0

/* Restore non-secure context */
add r0, sp, #SM_CTX_NSEC // r0 = sp + non_sec context
bl sm_restore_unbanked_regs // 恢复 non sec context 到 r0 位置

.sm_ret_to_nsec:
/*
* Return to non-secure world
*/
add r0, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R8) // r0 = sp + non_sec_context + non_sec_context_r8
ldm r0, {r8-r12} // 依次读 r8-r12 到 non_sec_context 中保存r8-r12 的位置

/* Update SCR */
read_scr r0
orr r0, r0, #(SCR_NS | SCR_FIQ) /* Set NS and FIQ bit in SCR */ // NS 置1 , FIQ 置1
write_scr r0 // 更新 SCR.NS 和 SCR.FIQ , 表明进入 non sec world
/*
* isb not needed since we're doing an exception return below
* without dependency to the changes in SCR before that.
*/

add sp, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R0)
b .sm_exit

================================ 来自于 非安全世界时的处理 ==========================
.smc_from_nsec:
/*
* As we're coming from non-secure world (NS bit set) the stack
* pointer points to sm_ctx.nsec.r0 at this stage. After the
* instruction below the stack pointer points to sm_ctx.
*/
sub sp, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R0)

bic r1, r1, #(SCR_NS | SCR_FIQ) /* Clear NS and FIQ bit in SCR */ //清除 SCR.NS 和 SCR.FIQ , 表明接下来的操作要在 sec world下进行
write_scr r1
isb

add r0, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R8)
stm r0, {r8-r12}

mov r0, sp
bl sm_from_nsec
cmp r0, #SM_EXIT_TO_NON_SECURE
beq .sm_ret_to_nsec // 同上, 更新SCR.NS 和 SCR.FIQ

/*
* Continue into secure world
*/
add sp, sp, #(SM_CTX_SEC + SM_SEC_CTX_R0)

.sm_exit:
pop {r0-r7}
rfefd sp! // 从异常帧中恢复并递减堆栈指针 //进到非安全世界上下文
END_FUNC sm_smc_entry

在 sec world 中为什么要将 SCR.FIQ 置 0?

而 armv8 中使用 ATF 来处理切换安全世界和非安全世界

ARMv8调用 smc 指令产生安全监控模式调用后,ARM 核会切换到 EL3中,然后读取 MVBAR 寄存器中的异常向量表的基地址来获取异常向量表的内容,并命中安全监控模式调用请求处理函数。

进入 handle_sync_exception 后对触发 sec monitor 调用的世界进行判定, 并设定需要切换到的那个世界的状态并恢复对应的 cpu 上下文.
根据调用 id 进入具体分支, 将 arm 运行模式切换为 EL1 或 EL0, 待 sec monitor 调用处理完毕后,重新进入 el3 继续剩下流程


触发 monitor 调用过程

调用 smc , arm 核进入 el3 中, 从 MVBAR 获取 vec table, 找到 smc 的处理函数.
最后进入到 handle_sync_exception 中, 调用 opteed_smc_handler 函数对该 smc 调用进行处理
判定该 smc 调用是 scr.NS 是来自于安全世界还是非安全世界, 再根据 smc id 决定是否恢复 normal world 的 context

在安全世界状态下触发 smc 后, 处理完请求后, 会再次触发 smc 重新进入 el3, 进入到 TEESMC_OPTEED_RETURN_CALL_DONE 的分支,在该分支中会保存安全世界状态的运行上下文并恢复正常世界状态的运行上下文,然后调用 SMC_RET4返回到正常世界状态中继续运行。

而在非安全世界状态下触发 smc 后, 在 opteed_smc_handler 函数中会调用 is_caller_non_secure 来判定当前安全监控模式调用是来自正常世界状态还是安全世界状态, 如果是来自于非安全世界, 则会保存非安全世界上下文, 恢复安全世界状态下的上下文, 处理调用请求结束后, 恢复非安全世界的上下文, 最后调用 SMC_RET4 返回到非安全世界继续运行.

总结

armv7 对 smc 的处理是在 monitor 状态下完成, 该处理代码集成在了 optee 中.
armv8 对 smc 的处理则是在 ATF 的 bl31阶段(类似 riscv M-mode 的 opensbi), 在 ATF 中 ARM 为兼容不同的 TEE 方案, 提供了集成接口, 各 TEE 方案按照规范完成世界切换.

中断与异常的处理

ARM 核处于安全世界状态(SWS)和正常世界状态(NWS)都具有独立的 VBAR 寄存器和中断向量表。而当 ARM 核处于 Monitor 模式或者 EL3时,ARM 核将具有独立的中断向量表和 MVBAR 寄存器。想实现各种中断在三种状态下被处理的统一性和正确性,就需要确保各种状态下中断向量表以及 GIC 的正确配置。ARM 的指导手册中建议在 TEE 中使用 FIQ,在 ROS 中使用 IRQ,即 TEE 侧会处理由中断引起的 FIQ 事件,而 Linux 内核端将会处理中断引起的 IRQ 事件。而由于 ATF 的使用,Monitor 状态或者 EL3下中断的处理代码将会在 ATF 中实现。(ATF 来处理更高级及跟敏感的中断)

对于 armv7 核, 中断与 arm 核每种状态的关系图:

中断分本地中断和外部中断, FIQ 被 TEE 侧处理, IRQ 被 REE 处理
如果在 monitor (armv7) 或 EL3 (armv8) 阶段产生了中断, 则处于 monitor 或 el3 阶段的软件使用 MVBAR 寄存器 vec table 中处理函数对 FIQ 和 IRQ 进行处理

GIC 寄存器

GIC 模块的寄存器分为中断分发寄存器(GICD) 和 cpu 接口寄存器 (GICC)
GICD 接收所有的中断源, 根据中断优先级判定是否响应中断, 及将该中断信号转发给相应的 cpu
GICC 同各个核连接, 当收到 GICD 的中断信号后, 由 GICC 决定是否将中断请求发给 arm 核

支持安全扩展的 GIC 模块将终端分为两组: group0 和 group1 中断.

  • armv7 上, g0为安全中断, g1 为非安全中断
  • armv8, g0 为安全中断且优先级最高, g1 分安全中断和非安全中断

GIC 根据中断所在 group 安全类型及当前 arm 核运行模式决定发送 FIQ 还是 IRQ.

1
2
3
#define INTR_TYPE_S_EL1      0        // 该中断应该由Secure EL1处理
#define INTR_TYPE_EL3 1 // 该中断应该由EL3处理
#define INTR_TYPE_NS 2 // 该中断应该由Normal World 处理

不同版本 GIC 对于上面的三种类型的中断产生不同的 FIQ 或 IRQ 事件. 再根据 SCR.FIQ 和 SCR.IRQ 位来决定该中断是否会触发进入 EL3 阶段

对于 armv7

前面提出的 armv7 下 sec world 下为什么将 FIQ 置 0, 置 0后, FIQ 中断会直接进入 FIQ 中断入口, 由 optee_os 进行处理, 而不会再进 monitor 的 vec table

armv8的 SCR.FIQ SCR.IRQ

1
2
3
4
5
6
7
* NS:   Non Secure Mode
* 0: Routing Model 0
* 1: Routing Model 1
* 值对应SCR寄存器中的bit[2:0],定义如下
* bit[0]: SCR.NS (0: Secure, 1: Non Secure)
* bit[1]: SCR.IRQ (0: enter IRQ mode 1: enter EL3 monitor)
* bit[2]: SCR.FIQ (0: enter FIQ mode 1: enter EL3 monitor)

GICv2设定Group0为安全中断,Group1为非安全中断。中断号属于哪个 Group 是由其在 GICD_IGROUPRn 寄存器中的值来决定的。当 GIC 接收到中断信号后,如果中断属于 Group0则发送 IRQ 信号到目标 CPU,中断属于 Group1则发送 FIQ 信号到目标 CPU。

与 GICv2相比 GICv3的主要改进有以下几点:

  • 在软件中断 (SGI) 方面新增中断路由模式(affinity rounting), 能支持更大范围的 cpu id
  • GICv3 对 Group1 的中断类型进一步细分. Group0 还是为安全中断且有最高优先级, 而 Group1 中断分为非安全中断 (GINS) 和安全中断 (GIS)
  • 在 FIQ/IRQ 都使能时, 属于 Group0的中断始终触发 FIQ, 属于 Group1的中断根据当前 cpu 工作模式和中断类型分别触发 FIQ 或 IRQ
当前处理器模式 Group0 Group1 Group1
G1S G1NS
Secure EL1/EL0 FIQ IRQ FIQ
Non Secure EL1/EL0 FIQ FIQ IRQ
Secure EL3 FIQ FIQ FIQ

异常向量

REE/TEE/monitor (v7) 或 EL3 (v8) 都可以接收中断, 存在两个 VBAR 和一个 MVBAR

  • REE VBAR 存放 linux 内核的异常 vec table
  • TEE VBAR 存放 tee 的异常 vec table
  • monitor 或 EL3 MVBAR 存放其自己的 vec table

ARMv7中 Monitor 模式的异常向量表

Monitor模式属于安全世界状态,用于实现ARM核安全世界状态与正常世界状态之间的切换,且该模式具有独立的中断向量表。使用MVBAR寄存器来保存该运行模式的中断向量表的基地址。在OPTEE初始化过程中会调用sm_init函数来初始化Monitor模式的配置,并将Monitor模式的中断向量基地址写入到MVBAR寄存器中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
FUNC sm_init , :
UNWIND( .fnstart)
mrs r1, cpsr //设置Monitor模式使用的栈
cps #CPSR_MODE_MON
sub sp, r0, #(SM_CTX_SIZE - SM_CTX_NSEC)
msr cpsr, r1
//将Monitor模式的异常向量表地址保存到r0寄存器
ldr r0, =sm_vect_table
//将Monitor模式的异常向量表基地址写入MVBAR寄存器中
write_mvbar r0
bx lr // 返回
END_FUNC sm_init

UNWIND( .fnstart)
UNWIND( .cantunwind)
b . /* 重启操作 */
b . /* 未定义指令操作 */
b sm_smc_entry /* smc异常处理函数 */
b . /* 执行时的abort操作 */
b . /* 数据abort操作 */
b . /* 预留 */
b . /* IRQ事件 */
b sm_fiq_entry /* FIQ中断处理入口函数 */
UNWIND( .fnend)
END_FUNC sm_vect_table

当在 Monitor 模式下接收到 FIQ 中断时,系统将会调用 sm_fiq_entry 函数对该 FIQ 中断进行处理

ARMv8中 EL3阶段的异常向量表

ARMv8使用 ATF 中的 bl31作为 EL3阶段的代码,其作用与 ARMv7中 Monitor 模式下运行的代码作用一致。在 ATF 的启动过程中,bl31通过调用 el3_entrypoint_common 函数来进行 EL3运行环境的初始化,在初始化过程中会执行 EL3阶段异常向量表的初始化,EL3的异常向量表的基地址为 runtime_exception_vectors。EL3异常向量表的内容如下:

从异常向量表来看,ARMv8架构中不管是 AArch32还是 AArch64,当在 EL3阶段产生了 FIQ 事件或者 IRQ 事件后,bl31将会调用 handle_interrupt_exception 宏来处理,该宏使用的参数就是产生的异常的标签。

OP-TEE 异常向量的配置

OP-TEE 异常向量的加载和配置会通过执行 thread_init_vbar 函数来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// aarch32
FUNC thread_init_vbar , :
/* Set vector (VBAR) */
write_vbar r0
bx lr
END_FUNC thread_init_vbar
DECLARE_KEEP_PAGER thread_init_vbar

// aarch64
FUNC thread_init_vbar , :
msr vbar_el1, x0
ret
END_FUNC thread_init_vbar
DECLARE_KEEP_PAGER thread_init_vbar

系统会到 VBAR 寄存器中获取 OP-TEE 的异常向量表基地址,然后根据异常类型获取到 FIQ 或 IRQ 事件的处理函数,并对不同的事件进行处理。针对不同的事件会调用线程向量表 thread_vector_table 变量中对应的处理函数来完成对该异常事件的处理。

thread_vector_table

optee 中会定义一个用于保存各种事件处理函数的线程向量表, 对 fast_smc std_smc FIQ cpu 关闭打开及系统关机和重启事件的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FUNC thread_vector_table , : , .identity_map
UNWIND( .cantunwind)
b vector_std_smc_entry
b vector_fast_smc_entry
b vector_cpu_on_entry
b vector_cpu_off_entry
b vector_cpu_resume_entry
b vector_cpu_suspend_entry
b vector_fiq_entry
b vector_system_off_entry
b vector_system_reset_entry
END_FUNC thread_vector_table
DECLARE_KEEP_PAGER thread_vector_table
#endif /*if defined(CFG_WITH_ARM_TRUSTED_FW)*/

armv8 下, 该线程向量表的地址会返回给 bl31, 当 EL3 阶段收到 smc 调用或者 FIQ 事件时可转到该 vec table 中对应的处理函数进行处理
armv7 下, 该变量被 monitor 模式下的程序(还是 optee)使用, 进而跳转到 vec table 对应的函数中处理

FIQ 处理

armv7


armv8

OP-TEE 对 FIQ 事件的处理

OP-TEE 启动时会调用 thread_init_vbar 函数来完成安全世界状态(SWS)的中断向量表的初始化,且在 GIC 中配置 FIQ 在安全世界状态时才有效。所以在安全世界状态中产生了 FIQ 事件时,CPU 将直接通过 VBAR 寄存器查找到中断向量表的基地址,并命中 FIQ 的处理函数。整个处理过程如图所示。

OP-TEE 对 IRQ 事件的处理

IRQ 事件一般由 REE 处理. 但当处于安全世界时, 系统产生的 IRQ 事件又不能丢弃. OPTEE 还是需要将 irq 转出来发给 REE

当系统在 ARM 核处于安全世界状态中产生 IRQ 事件时,系统通过 VBAR 寄存器获取到中断向量表的基地址,然后查找到 IRQ 对应的中断处理函数—— thread_irq_handler,使用该函数处理 IRQ 事件,整个处理过程的流程如图所示。

optee 收到 irq 后, armv7 中通过切换到 monitor 模式将该 irq 事件发送到 ree 侧进行处理
armv8 架构中 irq 中断会切换到 EL3 将该事件交给 REE 进行处理

完成了 IRQ 事件处理后, 会通过 smc 重新切回 monitor/EL3, 恢复安全世界状态被中断的线程

总结

armv7 安全世界状态包含 monitor 和 optee, 其中 monitor 的代码是包含在 optee_os 下的. 而在 armv8下安全世界包括 el3阶段的代码和 optee, el3阶段的代码在 ATF 的 bl31 中.
armv7 monitor 模式下运行的代码用来处理 fiq, armv8 在 el3阶段收到的 fiq 由 bl31 阶段的代码进行处理.
在安全世界中收到的 fiq 都是由 optee 的线程向量表中的 thread_fiq_handler 处理的
在安全世界中收到的 irq 都是由 optee 的线程向量表中的 thread_irq_handler 处理的, 转给 monitor/el3 进行转发, 最终转发给 REE 处理, 处理完成后, REE 需要发 smc 返回到 monitor/el3, 再进入到 optee 中恢复被 irq 打断的任务状态.

芯片上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
   0xbfc01000:  move    a2,t2
=> 0xbfc01004: lw a0,0(s0)
0xbfc01008: move a2,t2
0xbfc0100c: jal 0xbfc00a64
0xbfc01010: move a1,t3
0xbfc01014: bnez v0,0xbfc010a8
0xbfc01018: li a2,100
0xbfc0101c: li a1,4

0xbfc01000: jal 0xbfc00a64
0xbfc01004: lw a0,0(s0)
=> 0xbfc01008: move a2,t2
0xbfc0100c: jal 0xbfc00a64
0xbfc01010: move a1,t3
0xbfc01014: bnez v0,0xbfc010a8
0xbfc01018: li a2,100
0xbfc0101c: li a1,4


0xbfc01000: move a1,t3
0xbfc01004: lw a0,0(s0)
0xbfc01008: move a2,t2
=> 0xbfc0100c: jal 0xbfc00a64
0xbfc01010: move a1,t3
0xbfc01014: bnez v0,0xbfc010a8
0xbfc01018: li a2,100
0xbfc0101c: li a1,4

下载模式下调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
	la a2,		change_k0_cca
li a1, 0xf
ins a2, a1, 29, 1 // changed to KSEG1 address by setting bit 29
jalr a2


LEAF(change_k0_cca)
// NOTE! This code must be executed in KSEG1 (not KSGE0 uncached)
// Set CCA for kseg0 to cacheable
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
mfc0 TEMP1, C0_CONFIG // read C0_Config
li TEMP2, 2 // CCA for all others

set_kseg0_cca:
ins TEMP1, TEMP2, 0, 3 // insert K0
mtc0 TEMP1, C0_CONFIG // write C0_Config
ehb
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
jalr.hb zero, ra

END(change_k0_cca)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
Remote debugging using 192.168.66.149:3333
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
0x82c0ee7c in ?? ()
Undefined command: "dashboard". Try "help".
Loading section .text_init, size 0x2540 lma 0x84104000
Loading section .text_ram, size 0x4628 lma 0x84106540
Loading section .rodata, size 0xa10 lma 0x8410ab68
Loading section .data, size 0x28 lma 0x8410b578
Start address 0x84104000, load size 30112
Transfer rate: 21 KB/sec, 6022 bytes/write.
Execution start_address = 0x84104000
Fastdata access Failed
Falling back to non-bulk write
46496 bytes written at address 0x84100000
downloaded 46496 bytes in 3.464504s (13.106 KiB/s)
target was already halted
Breakpoint 1 at 0x84104000: file src/asm/start.S, line 46.
Continuing.

Program stopped.
__reset_vector () at src/asm/start.S:46
46 la a2, check_nmi
(gdb) disassemble change_k0_cca
Dump of assembler code for function change_k0_cca:
0x84104804 <+0>: nop
0x84104808 <+4>: nop
0x8410480c <+8>: nop
0x84104810 <+12>: nop
0x84104814 <+16>: nop
0x84104818 <+20>: nop
0x8410481c <+24>: nop
0x84104820 <+28>: nop
0x84104824 <+32>: nop
0x84104828 <+36>: nop
0x8410482c <+40>: nop
0x84104830 <+44>: nop
0x84104834 <+48>: nop
0x84104838 <+52>: nop
0x8410483c <+56>: nop
0x84104840 <+60>: nop
0x84104844 <+64>: nop
0x84104848 <+68>: nop
0x8410484c <+72>: nop
0x84104850 <+76>: nop
0x84104854 <+80>: mfc0 t6,c0_config
0x84104858 <+84>: li t7,2
0x8410485c <+88>: ins t6,t7,0x0,0x3
0x84104860 <+92>: mtc0 t6,c0_config
0x84104864 <+96>: ehb
0x84104868 <+100>: nop
0x8410486c <+104>: nop
0x84104870 <+108>: nop
0x84104874 <+112>: nop
0x84104878 <+116>: nop
0x8410487c <+120>: nop
0x84104880 <+124>: nop
0x84104884 <+128>: nop
0x84104888 <+132>: nop
0x8410488c <+136>: nop
0x84104890 <+140>: nop
0x84104894 <+144>: nop
0x84104898 <+148>: nop
0x8410489c <+152>: nop
0x841048a0 <+156>: nop
0x841048a4 <+160>: nop
0x841048a8 <+164>: nop
0x841048ac <+168>: nop
0x841048b0 <+172>: nop
0x841048b4 <+176>: nop
0x841048b8 <+180>: jalr.hb zero,ra
0x841048bc <+184>: nop
End of assembler dump.
(gdb) b *0xa410486c
Breakpoint 2 at 0xa410486c
(gdb) c
Continuing.

Program stopped.
0xa410486c in ?? ()
(gdb) mon mips32 cp0 config
0xa4310582
(gdb) mon mww 0x82d030b4 0x80000080
(gdb) x/12i 0xbfc00100
0xbfc00100: sdc2 $16,147(k1)
0xbfc00104: ldc1 $f7,16913(ra)
0xbfc00108: sw gp,-16950(a0)
0xbfc0010c: 0x7f9f292a
0xbfc00110: jalx 0xb02f4ffd
0xbfc00114: 0x720b0904
0xbfc00118: 0x6082ee6e
0xbfc0011c: lwc2 $15,4232(t7)
0xbfc00120: lwc1 $f1,-17410(a2)
0xbfc00124: ldc2 $13,-24540(t7)
0xbfc00128: ll t7,-9333(t0)
0xbfc0012c: 0x1feb0088
(gdb) restore ~/work_space/benji/splfdl/text_init.bin binary 0xbfc00100
Restoring binary file /home/liguang/work_space/benji/splfdl/text_init.bin into memory (0xffffffffbfc00100 to 0xffffffffbfc03e40)
(gdb) x/12i 0xbfc00100
0xbfc00100: lui a2,0x82a0
0xbfc00104: addiu a2,a2,1172
0xbfc00108: jr a2
0xbfc0010c: mtc0 zero,c0_count
0xbfc00110: nop
0xbfc00114: nop
0xbfc00118: nop
0xbfc0011c: nop
0xbfc00120: nop
0xbfc00124: nop
0xbfc00128: nop
0xbfc0012c: nop
(gdb) mon mww 0x82d030b4 0x80
(gdb) x/12i 0xbfc00100
0xbfc00100: lui a2,0x82a0
0xbfc00104: addiu a2,a2,1172
0xbfc00108: jr a2
0xbfc0010c: mtc0 zero,c0_count
0xbfc00110: nop
0xbfc00114: nop
0xbfc00118: nop
0xbfc0011c: nop
0xbfc00120: nop
0xbfc00124: nop
0xbfc00128: nop
0xbfc0012c: nop
(gdb) b *0xbfc00100
warning: GDB can't find the start of the function at 0xa410486c.

GDB is unable to find the start of the function at 0xa410486c
and thus can't determine the size of that function's stack frame.
This means that GDB may be unable to access that stack frame, or
the frames below it.
This problem is most likely caused by an invalid program counter or
stack pointer.
However, if you think GDB should simply search farther back
from 0xa410486c for code which looks like the beginning of a
function, you can increase the range of the search using the `set
heuristic-fence-post' command.
Breakpoint 3 at 0xbfc00100
(gdb) x/12i 0xbfc00100
0xbfc00100: lui a2,0x82a0
0xbfc00104: addiu a2,a2,1172
0xbfc00108: jr a2
0xbfc0010c: mtc0 zero,c0_count
0xbfc00110: nop
0xbfc00114: nop
0xbfc00118: nop
0xbfc0011c: nop
0xbfc00120: nop
0xbfc00124: nop
0xbfc00128: nop
0xbfc0012c: nop
(gdb) j *0xbfc00100
Continuing at 0xbfc00100.

Program stopped.
0xbfc00100 in ?? ()
(gdb) x/12i 0xbfc00100
=> 0xbfc00100: 0xf06275ff
0xbfc00104: 0xf06275ff
0xbfc00108: 0xf06275ff
0xbfc0010c: 0xf06275ff
0xbfc00110: 0xf06275ff
0xbfc00114: 0xf06275ff
0xbfc00118: 0xf06275ff
0xbfc0011c: 0xf06275ff
0xbfc00120: 0xf06275ff
0xbfc00124: 0xf06275ff
0xbfc00128: 0xf06275ff
0xbfc0012c: 0xf06275ff

fpga验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
❯ mgdb.microAptiv 8888
GNU gdb (GDB) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-pc-linux-gnu --target=mips-mti-elf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word".
Remote debugging using localhost:7777
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
0x00000000 in ?? ()
The target is assumed to be little endian
target state: halted
target halted in MIPS32 mode due to debug-request, pc: 0x82c476d8
"on" or "off" expected.
(microAptiv) mon mips32 cp0 config
0xa4310582
(microAptiv) x/i $pc
=> 0x0: nop
(microAptiv) x/i $pc
=> 0x0: nop
(microAptiv) mon mww 0xa2a00008 0x7ca41004
(microAptiv) mon mww 0xa2a00000 0x40048000
(microAptiv) mon mww 0xa2a00004 0x24050003
(microAptiv) mon mww 0xa2a0000c 0x40848000
(microAptiv) x/8i 0xa2a00000
0xa2a00000: mfc0 t6,$16
0xa2a00004: li t7,3
0xa2a00008: 0x7dee1004
0xa2a0000c: mtc0 t6,$16
0xa2a00010: nop
0xa2a00014: nop
0xa2a00018: nop
0xa2a0001c: nop
=> (microAptiv) b *0xa2a00000
Breakpoint 1 at 0xa2a00000
==> (microAptiv) j *0xa2a00000
Continuing at 0xa2a00000.

Program stopped.
0xa2a00000 in ?? ()
(microAptiv) disassemble 0xa2a00000,+0x20
Dump of assembler code from 0xa2a00000 to 0xa2a00020:
=> 0xa2a00000: mfc0 a0,$16
0xa2a00004: li a1,3
0xa2a00008: 0x7ca41004
0xa2a0000c: mtc0 a0,$16
0xa2a00010: nop
0xa2a00014: nop
0xa2a00018: nop
0xa2a0001c: nop
End of assembler dump.
(microAptiv) ni
warning: GDB can't find the start of the function at 0xa2a00000.

GDB is unable to find the start of the function at 0xa2a00000
and thus can't determine the size of that function's stack frame.
This means that GDB may be unable to access that stack frame, or
the frames below it.
This problem is most likely caused by an invalid program counter or
stack pointer.
However, if you think GDB should simply search farther back
from 0xa2a00000 for code which looks like the beginning of a
function, you can increase the range of the search using the `set
heuristic-fence-post' command.
warning: GDB can't find the start of the function at 0xa2a00004.
0xa2a00004 in ?? ()
(microAptiv) ni
warning: GDB can't find the start of the function at 0xa2a00008.
0xa2a00008 in ?? ()
(microAptiv) ni
warning: GDB can't find the start of the function at 0xa2a0000c.
0xa2a0000c in ?? ()
(microAptiv) i r a0
a0: 0xa4310583
(microAptiv) ni
warning: GDB can't find the start of the function at 0xa2a00010.
0xa2a00010 in ?? ()
(microAptiv) mon mips32 cp0 config
0xa4310583
(microAptiv) disassemble 0xa2a00000,+0x20
Dump of assembler code from 0xa2a00000 to 0xa2a00020:
0xa2a00000: mfc0 a0,$16
0xa2a00004: li a1,3
0xa2a00008: 0x7ca41004
0xa2a0000c: mtc0 a0,$16
=> 0xa2a00010: nop
0xa2a00014: nop
0xa2a00018: nop
0xa2a0001c: nop
End of assembler dump.
(microAptiv) mon mww 0x82d030b4 0x80000080
(microAptiv) restore ~/work_space/benji/splfdl/text_init.bin binary 0xbfc00100
Restoring binary file /home/liguang/work_space/benji/splfdl/text_init.bin into memory (0xffffffffbfc00100 to 0xffffffffbfc03cb0)
(microAptiv) x/8i 0xbfc00100
0xbfc00100: lui a2,0xbfc0
0xbfc00104: addiu a2,a2,1172
0xbfc00108: jr a2
0xbfc0010c: mtc0 zero,$9
0xbfc00110: nop
0xbfc00114: nop
0xbfc00118: nop
0xbfc0011c: nop
(microAptiv) mon mww 0x82d030b4 0x80
(microAptiv) b *0xbfc00100
Breakpoint 2 at 0xbfc00100
(microAptiv) j *0xbfc00100
Continuing at 0xbfc00100.

Program stopped.
0xbfc00100 in ?? ()
(microAptiv) mon mips32 cp0 config
0xa4310583
(microAptiv) x/12i $pc
=> 0xbfc00100: lui a2,0xbfc0
0xbfc00104: addiu a2,a2,1172
0xbfc00108: jr a2
0xbfc0010c: mtc0 zero,$9
0xbfc00110: nop
0xbfc00114: nop
0xbfc00118: nop
0xbfc0011c: nop
0xbfc00120: nop
0xbfc00124: nop
0xbfc00128: nop
0xbfc0012c: nop
(microAptiv) x/12i 0xbfc00100
=> 0xbfc00100: lui a2,0xbfc0
0xbfc00104: addiu a2,a2,1172
0xbfc00108: jr a2
0xbfc0010c: mtc0 zero,$9
0xbfc00110: nop
0xbfc00114: nop
0xbfc00118: nop
0xbfc0011c: nop
0xbfc00120: nop
0xbfc00124: nop
0xbfc00128: nop
0xbfc0012c: nop
(microAptiv) ni
warning: GDB can't find the start of the function at 0xbfc00100.
warning: GDB can't find the start of the function at 0xbfc00104.
0xbfc00104 in ?? ()
(microAptiv) x/12i 0xbfc00100
0xbfc00100: lui a2,0xbfc0
=> 0xbfc00104: addiu a2,a2,1172
0xbfc00108: jr a2
0xbfc0010c: mtc0 zero,$9
0xbfc00110: nop
0xbfc00114: nop
0xbfc00118: nop
0xbfc0011c: nop
0xbfc00120: nop
0xbfc00124: nop
0xbfc00128: nop
0xbfc0012c: nop
(microAptiv) file ~/u
uA_baremetal.elf uAptiv_Baremetal.elf ua.bin
(microAptiv) file ~/uA_baremetal.elf
Reading symbols from ~/uA_baremetal.elf...
warning: GDB can't find the start of the function at 0xbfc00104.
(microAptiv) restore ~/u
uA_baremetal.elf uAptiv_Baremetal.elf ua.bin
(microAptiv) restore ~/work_space/benji/xy-bootrom/objs_release/uA_baremetal.bin binary 0x82c00000
Restoring binary file /home/liguang/work_space/benji/xy-bootrom/objs_release/uA_baremetal.bin into memory (0xffffffff82c00000 to 0xffffffff82c1147f)
(microAptiv) x/8i 0x82c00000
0x82c00000 <__reset_vector at src/startup/start.S:41>: lui a2,0x82c0
0x82c00004 <__reset_vector+4 at src/startup/start.S:41>: addiu a2,a2,1280
0x82c00008 <__reset_vector+8 at src/startup/start.S:43>: jr a2
0x82c0000c <__reset_vector+12 at src/startup/start.S:43>: mtc0 zero,c0_count
0x82c00010 <_boot_rom+16 at src/startup/start.S:43>: nop
0x82c00014 <_boot_rom+20 at src/startup/start.S:43>: nop
0x82c00018 <_boot_rom+24 at src/startup/start.S:43>: nop
0x82c0001c <_boot_rom+28 at src/startup/start.S:43>: nop
(microAptiv) b *0x82c00000
Breakpoint 3 at 0x82c00000: file src/startup/start.S, line 41.
(microAptiv) j *0x82c00000
Continuing at 0x82c00000.

Program stopped.
__reset_vector () at src/startup/start.S:41
41 la a2, check_nmi
(microAptiv) ni
0x82c00004 41 la a2, check_nmi
(microAptiv) x/12i 0xbfc00000
0xbfc00000: lui a2,0x82c0
0xbfc00004: addiu a2,a2,0
0xbfc00008: jr a2
0xbfc0000c: nop
0xbfc00010: nop
0xbfc00014: nop
0xbfc00018: nop
0xbfc0001c: nop
0xbfc00020: nop
0xbfc00024: nop
0xbfc00028: nop
0xbfc0002c: nop
(microAptiv) x/12i 0xbfc00100
0xbfc00100: lui a2,0xbfc0
0xbfc00104: addiu a2,a2,1172
0xbfc00108: jr a2
0xbfc0010c: mtc0 zero,c0_count
0xbfc00110: nop
0xbfc00114: nop
0xbfc00118: nop
0xbfc0011c: nop
0xbfc00120: nop
0xbfc00124: nop
0xbfc00128: nop
0xbfc0012c: nop
(microAptiv) ni
43 jr a2
(microAptiv) x/8i $pc
=> 0x82c00008 <__reset_vector+8 at src/startup/start.S:43>: jr a2
0x82c0000c <__reset_vector+12 at src/startup/start.S:43>: mtc0 zero,c0_count
0x82c00010 <_boot_rom+16 at src/startup/start.S:43>: nop
0x82c00014 <_boot_rom+20 at src/startup/start.S:43>: nop
0x82c00018 <_boot_rom+24 at src/startup/start.S:43>: nop
0x82c0001c <_boot_rom+28 at src/startup/start.S:43>: nop
0x82c00020 <_boot_rom+32 at src/startup/start.S:43>: nop
0x82c00024 <_boot_rom+36 at src/startup/start.S:43>: nop
(microAptiv) x/12i 0xbfc00100
0xbfc00100: lui a2,0xbfc0
0xbfc00104: addiu a2,a2,1172
0xbfc00108: jr a2
0xbfc0010c: mtc0 zero,c0_count
0xbfc00110: nop
0xbfc00114: nop
0xbfc00118: nop
0xbfc0011c: nop
0xbfc00120: nop
0xbfc00124: nop
0xbfc00128: nop
0xbfc0012c: nop
(microAptiv) b *0xbfc00100
Note: breakpoint 2 also set at pc 0xbfc00100.
Breakpoint 4 at 0xbfc00100
(microAptiv) j *0xbfc00100
Continuing at 0xbfc00100.

Program stopped.
0xbfc00100 in ?? ()
(microAptiv) ni
warning: GDB can't find the start of the function at 0xbfc00100.
warning: GDB can't find the start of the function at 0xbfc00104.
0xbfc00104 in ?? ()
(microAptiv) x/12i 0xbfc00100
0xbfc00100: lui a2,0xbfc0
=> 0xbfc00104: addiu a2,a2,1172
0xbfc00108: jr a2
0xbfc0010c: mtc0 zero,c0_count
0xbfc00110: nop
0xbfc00114: nop
0xbfc00118: nop
0xbfc0011c: nop
0xbfc00120: nop
0xbfc00124: nop
0xbfc00128: nop
0xbfc0012c: nop
(microAptiv)
1
2
3
test 按下
The CRU Status regs are: 0x5801 0x3347
The CRU Status regs are: 0x1 0x3347

签名

签名文件

1
.\sign_file.exe -d -r .\driver_test_key\ecc_priv_key -b <待签文件路径> -e <签名后的文件路径> -s

验证是否验签正常

1
.\sign_file.exe -d -u .\driver_test_key\ecc_pub_key  -e <签名后的文件路径> -v

验签

验签接口 verifyFile

1
2
3
4
5
6
7
8
9
10
#pragma once
/*
* ecc verify file to check if sign by specific priv key
* input:
* fp: input file pointer by fopen
* fileSize: file size by stat.st_size
* out:
* return 0: verify success, -1: verify fail
*/
int verifyFile(FILE* fp, int fileSize);

上面的实现代码就是VerifyLibApi.h的内容

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#define _CRT_SECURE_NO_DEPRECATE
#include <iostream>
#include "VerifyLibApi.h"

int main()
{
struct stat dat_stat;
const char* dat_path = "C:\\Users\\1\\Documents\\test.dat";

if (stat(dat_path, &dat_stat) != 0) {
printf("stat %s failed:%s\n", dat_path, strerror(errno));
return -1;
}

FILE* fp = fopen(dat_path, "rb");
if (fp == NULL)
{
printf("can't read_file_bytes\n");
return -1;
}
if (0 != verifyFile(fp, dat_stat.st_size))
{
printf("verifyFile failed!\n");
if (fp != NULL) fclose(fp);
return -1;
}
if (fp != NULL) fclose(fp);
return 0;
}

VisualStudio 使用示例

上面使用的接口输出的库是静态库VerifyLib.lib

在VisualStudio 开发时, 可以按下面的示例配置使用该库

首先lib库给出的有两个版本, 分别是debug版本和release版本用的

debug和release都需要配置

debug配置示例

VC++目录 -> 库目录 中添加debug版本的库所在的文件夹的路径

image-20220314103457471

链接器->输入->附加依赖项 中添加对应的VerifyLib库的文件名

image-20220314103547303

release 版本的配置过程跟debug版本一样, 不再赘述

蒙哥马利算法(Montgomery Algorithm)|蒙哥马利约简、模乘

模乘是为了计算ab(mod M)。普通算法中,在计算模M时,利用的是带余除法,除法运算需要太多次乘法,计算复杂度较高

蒙哥马利算法的思想就是利用进制表示简化除法运算,转化成位运算。

给定大数 a, b. 素数模 m

目的是求 Z = ab(mod m)

转化为蒙哥马利域算式:

先计算 aR(mod m), bR(mod m), 其中R=2^k, k=log2(m)

第一次蒙哥马利约简:

Z’= aR * bR * R^-1 (mod m) = abR(mod m) (1)

第二次蒙哥马利约简:

Z = Z’ * R^-1(mod m) = abR * R^-1 (mod m) = ab (mod m) (2)

第二次约简后即可求出最终的结果.

蒙哥马利约简

核心思想是利用进制移位简化除法运算

Montgomery reduction(X,R,m):

参数:

  1. X ———– 输入操作数, 比如对应第一次蒙哥马利约简, aR*bR(mod m)即为X
  2. R ———– 根据模m选定的数R, 即上面的R
  3. m ———– 模 m

步骤:

  1. 计算m’=m^-1 (mod R), (m’ 也被称为mp), n = Xm’(mod R)
  2. 计算y=(X+nm)/R (mod m) — 将X+nm 右移k位得到最终结果y ( R=2^k, k=log2(m) )

分析:

  1. (X+nm)/R (mod m) === X * R^-1 (mod m) ?

​ 同余运算, 因为 nm/R (mod m) === 0, 所以相等

  1. n 是怎么得来的 ?

​ 这里n要满足算式:

​ X+nm === 0 (mod R) 只有这样, (X+nm)/R 才能将除法转化为进制移位计算

​ 根据R的定义, R要满足 gcd(R, m)=1, 即 R和m之间最大公约数是1, 即R和m 互质. 这里因为 m 一般为素数, 而R=2^k所以这个条件是满足的,

​ 根据扩展的欧几里得算法:

​ RR′−mm′=1 即 mm’=RR’-1

​ X+nm === 0 (mod R)

​ Xm’ + nmm’ === 0 (mod R)

​ Xm’ + n(RR’-1) === 0 (mod R)

​ Xm’ - n === 0 (mod R)

​ n === Xm’ (mod R)

蒙哥马利预计算

从上面的约简过程看, 预计算最重要的参数是m’(mp), 同时 R R^-1 (mod m) R^2 (mod m) 也是需要预计算的

即一次ab(mod m)需要两次蒙哥马利约简, 且输入其实不是ab 而是 a’b’ = ab * R^2, 所以需要提前计算 abR^2的值.

注意一个数的逆只有在取模时才有意义, 没有模哪来的逆, 逆也是对某个模而言的, 换了模,这个数的逆就变成另一个值了

buildroot 编译 cryptsetup

需要编译依赖包: libdevmapper libpopt libuuid libjson-c libcrypto (openssl包)
嵌入式平台编译比较麻烦, 交叉工具链会报很多头文件 库文件的错误, 建议直接由buildroot编译交叉工具链, 这样后续生成的库文件 头文件才能正常找到, 还有cmake 用到的.cmake 包, pkgconfig的路径等.

最终生成文件 cryptsetup veritysetup dmsetup libcryptsetup.so

测试

1
2
3
4
5
6
mkfs.ext4 -b 4096 fs.img 4096
veritysetup format fs.img hashtree.img
#as: Root hash: d004843ab1abc42dbdcf320b7189d871536cdf6e5e6e2c03c2c0fd20dfc2fe9a
记下$root_hash
veritysetup create vroot fs.img hashtree.img $root_hash
mount /dev/mapper/vroot mount_point

编译kernel 支持dm-verity

1
2
3
4
5
make menuconfig
# Find the following options in the menuconfig and change them as described:
# Device Drivers → Multiple devices driver support (RAID and LVM) → Device mapper support → change to * (YES)
# Device Drivers → Multiple devices driver support (RAID and LVM) → Device mapper support → DM "dm-mod.create=" parameter support → change to * (YES)
# Device Drivers → Multiple devices driver support (RAID and LVM) → Device mapper support → Verity target support → change to * (YES)

kernel 调试

去掉优化

子模块中的Makefile可以通过CFLAGS_xxx.o += -O0 去掉某个源文件的编译优化
但还是有很多文件不支持 -O0 编译, 可以再细分, 按函数就行 -O0 编译
__attribute__((optimize("O0")))

stm32_750

中断耗时 29264

算法 数据量 耗时 数据量 耗时
aes-cbc-128 40k 199368 64k (342000+291048)/2-29264=287260
aes-cbc-192 40k 178104 64k (323104+291408)/2-29264=277992
aes-cbc-256 40k 161520 64k (294192+271480)/2-29264=253572
sha2-224 40k 190392-28120+5904 = 168176 64k 287528-28120+5904 = 265312
sha2-256 40k 189904-28102+5776 = 167578 64k 286184-28102+5776 = 263858
crc32 (0x04C11DB7) 40k 140136 64k 223754
crc16-ccitt 40k 142600 64k 223853
crc8-ccitt 40k 139858 64k 223461
TRNG 40k 1130608 64k 1793120

sha2-256 4k 43504+21264-28120= 36648

1
2
c "1/(480*1000*1000) * 36648" = 0.00007 s //for hash-256 4k
c "1/(480*1000*1000) * 167578 / 40 * 1024" = 0.009s

1M 数据时间

1
2
3
c "1/(480*1000*1000)*(199368)/40*1024" = 0.01s   //aes-128 750
c "1/(400*1000*1000)*(626550)/40*1024" = 0.04s // aes-256 cip
c "1/(400*1000*1000)*(1002480/64)*1024" = 0.04s // aes-256 cip
1
2
c "64*1024*8/1793120" = 0.3 bit/cycle    //trng
c "1/(480*1000*1000) * 167578" = 0.00035s //sha-256 40k

CIP_1901

算法 数据量 耗时 数据量 耗时
aes-cbc-128 40k 462550 64k 740032
aes-cbc-256 40k 626550 64k 1002480
sha2-256 40k 174700 64k 279268 512k 2237620
sha2-384 40k 113784 64k 181788 512k 1518852
crc32 (0x04C11DB7) 40k 128204 64k 204158
crc16-ccitt 40k 129834 64k 205702
crc8-ccitt 40k 128184 64k 204164
ecc-192-sign 16 609418
ECC-224-sign 16 745262
ECC-256-sign 16 870176
ECC-384-sign 16 1571708
ECC-192-verify 16 6753310
ECC-224-verify 16 8529408
ECC-256-verify 16 11227626
ECC-384-verify 16 25775968
ECC-256-sign 32 870134
ECC-384-sign 32 1571634
ECC-256-verify 32 11489984
ECC-384-verify 32 26295924
Ecc-384-sign 48 1571480
ECC-384-verify 48 25897978
1
2
c "1/(400*1000*1000)*25897978" = 0.0647s
c "1/(400*1000*1000)*11489984" = 0.028 s //for ecc-256 verify

security-boot

分区 大小 校验方式 硬件时间
spl 1M aes256 + hash256 + ecc256 0.01 + 0.009 + 0.028 = 0.047
bootloader 2M aes256 + hash256 + ecc256 (0.01 + 0.009 )*2 + 0.028 = 0.066
tz 4M aes256 + hash256 + ecc256 (0.01 + 0.009)*4 + 0.028 = 0.104
dtbo 16M aes256 + hash256 + ecc256 (0.01 + 0.009)*16 + 0.028 = 0.332
boot 32M aes256 + hash256 + ecc256 (0.01 + 0.009)*32+ 0.028 = 0.636
system 40G ecc256 + dm-verity 0.028 + …

校验链:

bootrom -> spl -> tz -> bootloader -> dtbo -> boot-> … system

dm-verity hash-tree

verity-40G

verity-40G

根据 hashtree 的生成方式,以 40G 的镜像为例:

1)按照 4K 大小划分,将40G 大小的镜像依顺序划分可得到 10485760 个 4k 大小的块

2)对这 10485760 块数据块进行第一层(Level 0) hash 计算,由于 SHA256(具体的hash算法可配置,此例以SHA256为参考) 计算出来的hash值占 256 个字节,一个 4K 的块可以存储 128 个hash值,所以存储这 10485760 块数据块的hash值需要花费 81920 块

3)对第一层存储 hash 值的数据块进行第二层(Level 1) 的 hash 计算,同理,计算这 81920 块hash数据块需要花费 640 块

  1. 对第二层存储 hash 值的数据块进行第二层(Level 2) 的 hash 计算,同理,计算这 640 块hash数据块需要花费 5 块

5)对第三层存储 hash 值的数据块进行第三层(Level 3) 的 hash 计算,由于第二层的hash数据块小于128块,所以第三层是最后一层,直接计算得到 root hash 数据块(不够4K大小补齐0)。

校验:

  1. 假设目前正需要读取第 200000 块的数据块,通过前面哈希树的构造,可以比较快速的计算出在 Level 0 层,也就是直接对应这个数据块的hash值存储位置,通过对 128 相除以及求余的方式就可以分别计算出该 hash 值存储在具体某一块(块A)以及这一块上的偏移(偏移A)。
  2. 确定了 Level 0 层的具体块A后,利用相同的方式可以得到 Level 0 块A在 Level 1 层存储其 hash 值的块B位置以及块B上的偏移(偏移B)
  3. 同上可以定位到 Level 1 层块 B 在 Level 2 上的数据块上偏移(偏移C)
  4. 最终定位level2 层 在 level 3 数据块上的偏移 (偏移 D)

计算:

  1. 计算level3 块(只有一块)的hash 256值, 即 root hash, root hash与verity table的hash 比较, 如果不一致则说明被非法修改了
  2. 计算level2 层 (偏移C) 所在块的 hash, 与level3 层偏移D处的 32bytes 的hash 比较, 如不一致则说明 level2 层偏移 C 所在块被非法修改了
  3. 计算level1 层(偏移B) 所在块的 hash , 与level2 层偏移 C 处的 32bytes的hash值比较, 如不一致则说明level 1 层偏移 B 所在块被非法修改了
  4. 计算 level 0 层(偏移A) 所在块的 hash, 与 level 1层偏移 B 处的 32bytes的hash值比较, 如不一致则说明 level 0 层的偏移 A 处的所在块被非法修改了
  5. 计算 raw data 层(被访问的原始数据) 所在块的 hash , 与level 0 层偏移 A 处的 32 bytes 的hash 值比较, 如不一致则说明 raw data层现在正在访问的数据被非法修改了

因此 read 40G分区上的一个4k 块额外需要 hash256(4k) 5次 (0.00007s *5) 时间.

生成从flash启动的文件

boot模式启动后自动运行

烧写规范

目前仅支持烧写spl fdl 和 大核的bin, ua-validation的bin 只能先伪装成spl来测试boot流程.

romcode 固化的代码中将spl下载到了ahbsram区域, 因此ua的bin也会下载到ahbsram上, 最大只能到128k

小核镜像打包

当前只能做普通非加密模式的bin, ua-validation生成的产物

  • validation/uAptiv_Baremetal.elf  

  • validation/uAptiv_Baremetal.bin 未做header包裹的bin, 不能直接用

  • validation/spl_non_sec.bin 生成了header, 可以作为spl烧写到flash 中, 给到烧机工具, 填到spl槽位

单独打包小核镜像

本节没有特殊需求, 可以不用看

如果需要根据uAptiv_Baremetal.elf文件重新打包出能烧写到flash中的符合规范的bin, 请参考下面的步骤:

在代码根目录下:

1
2
3
4
5
6
7

cd validation;

mips-mti-elf-objcopy -O binary uAptiv_Baremetal.elf uAptiv_Baremetal.bin

../tools/sign_tool.py -n --ua --source . --out .

生成的文件spl_non_sec.bin 给到烧机工具即可

如果没有单独打包小核镜像的需求, 单独打包小核镜像 小节直接忽略就行, 默认编译结果已经包含

大核镜像打包

当前只能做普通非加密模式的bin, ia-validation生成的产物

  • ia-validation/iAptiv_Baremetal.elf

  • ia-validation/iAptiv_Baremetal.bin  未做header包裹的bin, 不能直接用

  • ia-validation/ia_non_sec.bin        生成了header, 可以作为ia烧写到flash 中, 给到烧机工具, 填到ia槽位

单独打包大核镜像

本节没有特殊需求, 可以不用看

如果需要根据iAptiv_Baremetal.elf文件重新打包出能烧写到flash中的符合规范的bin, 请参考下面的步骤:

在代码根目录下:

1
2
3
4
5
6
7
8
9
10
11

cd ia-validation;

mips-mti-elf-objcopy -R .text_init -O binary iAptiv_Baremetal.elf iAptiv_Baremetal_App.bin

mips-mti-elf-objcopy -j .text_init -O binary iAptiv_Baremetal.elf iAptiv_Baremetal_Boot.bin

cat iAptiv_Baremetal_Boot.bin iAptiv_Baremetal_App.bin > iAptiv_Baremetal.bin

../tools/sign_tool.py -n --ia --source . --out .

生成的文件ia_non_sec.bin 给到烧机工具即可

如果没有单独打包大核镜像的需求,  单独打包大核镜像小节 直接忽略就行, 默认编译结果已经包含

烧写大核启动镜像

启动到大核需要spl转接, 不是上面 ua-validation, 下载这里的spl_non_sec_real.bin

烧写大核时需要同时把 fdl spl (spl_non_sec_real.bin) 和 当前编出的大核的bin(ia_non_sec.bin) 烧入对应槽

2230995-20210517010440573-253963743

性能指标

相位噪声、杂散以及瞬态响应

相位噪声

在频域中,这通常被认为是噪声功率相对于载波功率的密度

频谱分析仪测试相位噪声的一个示例。

img

杂散

杂散可以被认为是集中在与载波的特定偏移处的噪声。

杂散有很多种,它们可能有多种原因,但其中大多数发生在非常可预测的偏移处。 杂散倾向于出现在鉴频鉴相器频率、输入参考频率、通道间隔和几分之一通道间隔的倍数处。

img

瞬态响应和锁定时间

锁定时间通常被认为是在 N 分频器值改变后 PLL 稳定到给定频率容差所需的时间量。锁定时间定义可以扩展到包含相位扰动或容差表示为相位误差而不是频率误差的情况。

锁定时间是 PLL 改变频率所需的时间, 当 PLL 切换频率时,无法传输数据,因此 PLL 的锁定时间必须锁定足够快,以免降低数据速率。 锁定时间也可能与功耗有关。

软件改动

修改pll分频系数, 测试响应时间

CRU/Power 测试 case

CRU/Power 测试关闭uart0 时钟 SOC相关 可选 不测 不测 test_disable_uart0 测试关闭uart0 时钟, 观察uart0 是否打印log, 示波器看uart0 时钟是否关闭, 测试空载电流
CRU/Power 测试开启uart0 时钟 SOC相关 可选 不测 不测 test_enable_uart0 测试开启uart0 时钟,观察uart0 是否打印log, 示波器看uart0 时钟是否关闭,测试空载电流
CRU/Power 测试关闭uart1 时钟 SOC相关 可选 不测 不测 test_disable_uart1 测试关闭uart1 时钟,观察uart1 是否打印log, 示波器看uart1 时钟是否关闭,测试空载电流
CRU/Power 测试开启uart1 时钟 SOC相关 可选 不测 不测 test_enable_uart1 测试开启uart1 时钟,观察uart1 是否打印log, 示波器看uart1 时钟是否关闭,测试空载电流
CRU/Power 测试关闭uart2时钟 SOC相关 可选 不测 不测 test_disable_uart2 测试关闭uart2时钟,观察uart2 是否打印log, 示波器看uart2 时钟是否关闭,测试空载电流
CRU/Power 测试开启uart2 时钟 SOC相关 可选 不测 不测 test_enable_uart2 测试开启uart2 时钟,观察uart2 是否打印log, 示波器看uart2 时钟是否关闭,测试空载电流
CRU/Power 测试关闭ao qspi0 时钟 SOC相关 可选 不测 不测 test_ao_qspi_disable 测试关闭ao qspi0 时钟, 测试qspi0 是否正常工作, 示波器看qspi0 时钟是否关闭,测试空载电流
CRU/Power 测试打开ao qspi0 时钟 SOC相关 可选 不测 不测 test_ao_qspi_enable 测试打开ao qspi0 时钟, 测试qspi0 是否正常工作, 示波器看qspi0 时钟是否关闭,测试空载电流
CRU/Power 测试关闭pd1 qspi1时钟 SOC相关 可选 不测 不测 test_pd1_qspi_disable 测试关闭pd1 qspi1时钟, 测试qspi1 是否正常工作,,示波器看qspi1 时钟是否关闭,测试空载电流
CRU/Power 测试打开pd1 qspi1 时钟 SOC相关 可选 不测 不测 test_pd1_qspi_enable 测试打开pd1 qspi1 时钟, 测试qspi1 是否正常工作, 示波器看qspi1 时钟是否关闭,测试空载电流
CRU/Power 测试关闭ao 外设时钟 SOC相关 可选 不测 不测 test_ao_peri_disable 测试关闭ao 外设时钟, 测试ao 外设是否可以工作, 测试空载电流
CRU/Power 测试关闭pd1 外设时钟 SOC相关 可选 不测 不测 test_pd1_peri_disable 测试关闭pd1 外设时钟, 测试pd1 外设是否可以工作, 测试空载电流

外设相关的疑问

image-20220208140926281

例如pd1上 qspi 有两个时钟的enable mask

pd1_hclk_qspi 和 pd1_refclk_qspi, 很多外设都有两个enable mask, 如果要关闭该外设的时钟,需要设置哪个

  1. Power_domain_1_bus_clock_enable (offset = 0xc)

image-20220208141234617

  1. Power_domain_1_peripheral_clock_enable (offset = 0x18)

image-20220208141343394

image-20220208141428930

低功耗模式

ao low power mode

vad 在ao 域上, 可以关闭pd1? ao 需要从active mode 切换到 low power mode上.

ao 切换到 low power mode 后, ao_root_clk 就变成 12.288MHZ了? 在这个频率下vad ip是否可以正常工作? 是用的ao_i2s_clk的频率吗? dma2 是用的ahb_clk吗, 在12.288MHz下是否可以正常工作

ao 从 active mode 切换到 low power mode 除了设置 power_control.bit[0] 外需要哪些前置步骤和后置步骤

通过中断唤醒ao 域时, 是否需要重新走ao 和 pd1 状态机的流程?

ao_active ao_lp 模式怎么相互切换?

ldo怎样enable?

low_power_mode 到 active 状态切换以及开启所有需要工作的时钟 的响应时间

lp->active 状态时的功耗测试

默认时钟频率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
CLOCK_FREQ_1M                             1000k
MAIN_CLOCK_REF 12288k
PLL_CLOCK_OUT 400000k
CPU_ROOT_CLOCK 400000k
IAPTIV_CORE_CLOCK 400000k
UAPTIV_CORE_CLOCK 200000k
RTC_CLK_REF 32k
MIPS_PLL_CLK 400000k
MIPS_ROOT_CLK 400000k
MIPS_IAPTIV_MIPS_CLK 400000k
MIPS_IAPTIV_AXI_CLK 200000k
MIPS_IAPTIV_AHB_CLK 100000k
MIPS_IAPTIV_APB_CLK 50000k
MIPS_IAPTIV_PERI_CLK 200000k
MIPS_IAPTIV_QSPI_CLK 400000k
MIPS_IAPTIV_UART_CLK 36864k
MIPS_IAPTIV_I2C_CLK 50000k
MIPS_IAPTIV_USBPHY_CLK 25000k
MIPS_UAPTIV_AHB_CLK 200000k
MIPS_UAPTIV_APB_CLK 100000k
MIPS_UAPTIV_QSPI_CLK 400000k
MIPS_UAPTIV_UART_CLK 36864k
MIPS_UAPTIV_I2S_CLK 12288k
MIPS_UAPTIV_GPIO_CLK 1024k
MIPS_UAPTIV_RTC_CLK 32k
PD1_BUS_CLK_AXI_DEFAULT 200000k
PD1_BUS_CLK_AHB_DEFAULT 100000k
PD1_BUS_CLK_APB_DEFAULT 50000k
PD1_BUS_CLK_PERI_DEFAULT 200000k
PD1_PERI_CLK_QSPI_REF_DEFAULT 400000k
PD1_PERI_CLK_UART_DEFAULT 36864k
PD1_PERI_CLK_I2C_DEFAULT 50000k
PD1_PERI_CLK_SPI_DEFAULT 50000k
PD1_PERI_CLK_USBPHY_DEFAULT 25000k
PD1_UART1_CLK 36864k
PD1_UART2_CLK 36864k
AO_QSPI_PCLK 400000k
AO_AHB_CLK 200000k
AO_APB_CLK 100000k
AO_WDT_PCLK 100000k
AO_GPIO_PCLK 1024k
AO_TIMER_PCLK 100000k
AO_UART0_CLK 36864k
AO_BUS_CLK_AHB_DEFAULT 200000k
AO_BUS_CLK_APB_DEFAULT 100000k
AO_PERI_CLK_QSPI_REF_DEFAULT 400000k
AO_PERI_CLK_UART_DEFAULT 36864k
AO_PERI_CLK_I2S_DEFAULT 12288k
AO_PERI_CLK_PDM_DEFAULT 12288k
AO_PERI_CLK_VAD_DEFAULT 12288k
AO_PERI_CLK_GPIO_DEFAULT 1024k